﻿#region Using

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

#endregion

namespace MailDeliveryHelper.BusinessLogic
{
  /// <summary>
  /// Campaignifier is used to apply campaign parameters to links in an HTML string, 
  /// to make Web Analytics providers able to track campaigns.
  /// 
  /// Instead of linking direct to: http://www.example.com - linking to http://www.example.com?utm_source=newsletter&utm_media=email 
  /// makes Google Analytics able to categorize all trafic from this link into a campaign.
  /// </summary>
  public class Campaignifier
  {
    #region Fields

    private const string URL_PARAMETER_FORMAT = "&{0}={1}";

    #endregion

    #region Properties

    private Dictionary<string, string> _CampaignParameters;

    /// <summary>
    /// Gets or sets the campaign parameters.
    /// </summary>
    /// <value>The campaign parameters.</value>
    public Dictionary<string, string> CampaignParameters
    {
      get 
      {
        if (_CampaignParameters == null)
          _CampaignParameters = new Dictionary<string, string>();

        return _CampaignParameters; 
      }
      set { _CampaignParameters = value; }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Campaignifies the HTML. All links in the HTML string, gets the CampaignParameters appended.
    /// </summary>
    /// <param name="html">The HTML.</param>
    /// <returns>An HTML string with all links campaignified.</returns>
    public string CampaignifyHtml(string html)
    {
      if (CampaignParameters == null || CampaignParameters.Count == 0)
        return html;

      string parameterString = String.Empty;

      // Create campaign parameter string to use in all links.
      foreach (string key in CampaignParameters.Keys)
      {
        string value = String.Empty;
        CampaignParameters.TryGetValue(key, out value);
        parameterString += String.Format(URL_PARAMETER_FORMAT, key, value);
      }

      // Find all links, and add the parameter string to the URL.
      html = html.Replace(Environment.NewLine, "¤#¤");

      int deviation = 0;	// Controls deviation in regex replace indexes when length of file changes
      Regex rxLinks = new Regex("<\\s*a[^r][^>]*>(.*?)<\\s*/a\\s*>", RegexOptions.IgnoreCase);
      Regex rxHref = new Regex("(?:href\\s*=)(?:[\\s\"']*)(.*?)(?:[\\s>\"'])", RegexOptions.IgnoreCase);
      Regex rxLinkReplacer;
      MatchCollection mcLinks = rxLinks.Matches(html);
      Match mcHref;

      string linkValue;
      string href;
      string newUrl = string.Empty;

      //############################################################
      //	Parse normal links
      //############################################################
      if (mcLinks.Count > 0)
      {
        for (int i = 0; i < mcLinks.Count; i++)
        {
          // Build Campaign Url
          linkValue = mcLinks[i].Value;
          mcHref = rxHref.Match(linkValue);
          if (mcHref.Value.Contains("http://"))
          {
            href = mcHref.Value.Substring(6, mcHref.Value.Length - 7).Replace("¤#¤", " ");
          }
          else
          {
            href = mcHref.Value;
          }

          // Check if href ends with /. If so - remove it
          if (href.EndsWith("/"))
            href = href.Substring(0, href.Length - 1);

          // Replace href in text
          rxLinkReplacer = new Regex(String2Regex(href));
          newUrl = AddParameterToUrl(href, parameterString);
          html = rxLinkReplacer.Replace(html, newUrl, 1, mcLinks[i].Index - deviation);
          deviation += href.Length - newUrl.Length;
        }
      }
      deviation = 0;
      rxLinks = null;
      rxHref = null;

      //############################################################
      //	Parse image maps
      //############################################################
      Regex rxImaps = new Regex("<area[^>]*>", RegexOptions.IgnoreCase);
      Regex rxCoords = new Regex("(?:coords\\s*=)(?:[\\s\"']*)(.*?)(?:[\\s>\"'])", RegexOptions.IgnoreCase);
      MatchCollection mcImaps = rxImaps.Matches(html);
      Match mcCoords;

      if (mcImaps.Count > 0)
      {
        string sCoords;

        for (int i = 0; i < mcImaps.Count; i++)
        {
          mcCoords = rxCoords.Match(mcImaps[i].Value);
          sCoords = mcCoords.Value.Substring(8, mcCoords.Value.Length - 9).Replace("¤#¤", " ").Trim();
          mcHref = rxHref.Match(mcImaps[i].Value);
          href = mcHref.Value.Substring(6, mcHref.Value.Length - 7).Replace("¤#¤", " ");

          // Check if Href ends with /. If so - remove it
          if (href.EndsWith("/"))
            href = href.Substring(0, href.Length - 1);

          // Replace Href in text
          rxLinkReplacer = new Regex(String2Regex(href));
          newUrl = AddParameterToUrl(href, parameterString);
          html = rxLinkReplacer.Replace(html, newUrl, 1, mcImaps[i].Index - deviation);
          deviation += href.Length - newUrl.Length;
        }
      }
      deviation = 0;
      rxLinkReplacer = null;

      html = html.Replace("¤#¤", Environment.NewLine);

      return html;
    }

    /// <summary>
    /// Adds the parameter to URL.
    /// </summary>
    /// <param name="baseUrl">The base URL.</param>
    /// <param name="parameterString">The parameter string.</param>
    /// <returns>The baseUrl with the parameterString appended.</returns>
    private string AddParameterToUrl(string baseUrl, string parameterString)
    {
      if (baseUrl.Contains("?"))
      {
        if (baseUrl.EndsWith("&"))
        {
          baseUrl = baseUrl.Substring(0, baseUrl.Length - 1);
        }

        baseUrl += parameterString;
      }
      else
      {
        baseUrl += "?" + parameterString.Substring(1);
      }

      return baseUrl;
    }

    private string String2Regex(string text)
    {
      text = text.Replace("\\", "\\\\");
      text = text.Replace(".", "\\.");
      text = text.Replace("|", "\\|");
      text = text.Replace("*", "\\*");
      text = text.Replace("?", "\\?");
      text = text.Replace("+", "\\+");
      text = text.Replace("(", "\\(");
      text = text.Replace(")", "\\)");
      text = text.Replace("{", "\\{");
      text = text.Replace("}", "\\}");
      text = text.Replace("[", "\\[");
      text = text.Replace("]", "\\]");
      text = text.Replace("^", "\\^");
      text = text.Replace("$", "\\$");
      return text;
    }

    #endregion
  }
}
